﻿/* ----------------------------------------------------------------------------
 * Copyright (c) Philip Stears 
 *
 * This source code is subject to terms and conditions of the Microsoft Public 
 * License (Ms-PL).
 * 
 * By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 * 
 * To request a copy of the Microsoft Public License, send a request by email
 * to license@connectbasic.net.
 *
 * This notice must not be removed from this software.
 * ---------------------------------------------------------------------------- */

namespace ConnectBasic.ObjectModel
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using ConnectBasic.Execution.Binding;
    using Microsoft.Scripting;
    using DlrExpression = Microsoft.Linq.Expressions.Expression;

    /// <summary>
    /// An expression which invokes a member.
    /// </summary>
    public class InvokeExpression : ExpressionBase
    {
        private ExpressionBase mTarget;
        private string mMemberName;
        private ReadOnlyCollection<ExpressionBase> mArguments;

        /// <summary>
        /// Initializes a new instance of the <see cref="InvokeExpression" /> class.
        /// </summary>
        /// <param name="target">The expression which returns the object on which the member should be invoked.</param>
        /// <param name="memberName">The name of the member to be invoked.</param>
        /// <param name="arguments">The expressions which give the arguments to the member to be invoked.</param>
        /// <param name="expressionSpan">
        /// The span which describes the span in the source text from which the 
        /// expression was derived.
        /// </param>
        public InvokeExpression(SourceSpan expressionSpan, ExpressionBase target, string memberName, IEnumerable<ExpressionBase> arguments) :
            base(expressionSpan)
        {
            mTarget = target;
            mMemberName = memberName;
            mArguments = arguments == null ? null : new ReadOnlyCollection<ExpressionBase>(arguments as IList<ExpressionBase> ?? new List<ExpressionBase>(arguments));
        }

        /// <summary>
        /// Gets the expression which returns the object on which the member should be invoked.
        /// </summary>
        public ExpressionBase Target
        {
            get { return mTarget; }
        }

        /// <summary>
        /// Gets the name of the member to be invoked.
        /// </summary>
        public string MemberName
        {
            get { return mMemberName; }
        }

        /// <summary>
        /// Gets the arguments to the member to be invoked, or a null reference if not applicable.
        /// </summary>
        public IEnumerable<ExpressionBase> Arguments
        {
            get { return mArguments; }
        }

        /// <inheritdoc />
        public override string GetDebuggerDisplay()
        {
            string argumentSuffix = "";

            if (mArguments != null && mArguments.Count > 0)
            {
                string[] argumentDebuggerDisplay = new string[mArguments.Count];

                for (int i = 0; i < mArguments.Count; i++)
                {
                    argumentDebuggerDisplay[i] = mArguments[i].GetDebuggerDisplay();
                }

                argumentSuffix = "(" + string.Join(", ", argumentDebuggerDisplay) +  ")";
            }

            return "(Invoke " + mMemberName + argumentSuffix + " on " + mTarget.GetDebuggerDisplay() + ")";
        }

        /// <inheritdoc />
        public override Microsoft.Linq.Expressions.Expression Compile(ConnectBasic.Execution.BasicCodeGenerator gen)
        {
            List<DlrExpression> arguments = new List<Microsoft.Linq.Expressions.Expression>();

            // First argument is the invocation target
            arguments.Add(mTarget.Compile(gen));

            // Add the remaining arguments
            if (mArguments != null)
            {
                arguments.AddRange(from arg in mArguments select arg.Compile(gen));
            }

            // Construct the dynamic invocation
            var invokeMember = DlrExpression.Dynamic(
                                new BasicInvokeMemberBinder(mMemberName, new CallInfo(arguments.Count())),
                                typeof(object),
                                arguments);

            return invokeMember;
        }
    }
}
